/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

/**
 * 首先通过使用逻辑进行代码逻辑分析
 * const promise = new Promise((resolve, reject) => {
 *  // 成功时调用 resolve，失败时调用 reject
 * });
 * 1、promise使用时通过new关键字进行实例化操作，因此可以确定promise是一个类
 * 2、实例化promise时需要传入一个函数（执行器），并且执行器会有两个形参，通过resolve改变为状态到成功，通过reject改变状态到失败，只可变更一次
 * 3、常见的执行器内部会有ajax等异步操作，执行完毕后才返回结果
 * promise.then().then(() => {}, () => {})
 * 4、通过实例化对象调用then方法，首先确定拥有实例化方法then，其次then方法可以进行链式调用，且方法参数可选，参数为空、一个、两个
 * 5、参数分别对应：空时则内部默认创建成功方法并执行，且返回的是promise实例，以便继续链式，一个时对应成功，两个时对应成功和失败
 * 6、为防止then内部返回promise实例本身，因此需要对每次的返回进行判断，本身时抛出错误，普通值直接返回并包装成promise，promise对象时调用其then，并传递相应回调
 */


const PENDING = 'pending'; // 常量 - 请求中状态
const FULFILLED = 'fulfilled'; // 常量 - 成功状态
const REJECTED = 'rejected'; // 常量 - 失败状态

//首先声明一个类
class MyPromise {
  // 通过构造函数接收执行器
  constructor(exector) {
    // 为增加代码健壮性，使用try catch进行错误兼容
    try {
      exector(this.resolve, this.reject);
    } catch (error) {
      this.reject(error); // 失败时则直接调用失败回调，并把相应错误传递回去
    }
  }

  status = PENDING; // 记录当前状态，默认为请求中
  value = undefined; // 记录成功结果，默认为undefined
  reason = undefined; // 记录失败结果，默认为undefined
  successCallback = []; // 因then可以多次链式调用，因此成功回调必然是数组形式，以便存储所有成功回调
  faillCallback = []; // 失败同理

  // 成功时回调, 接受一个参
  resolve = value => {
    // 执行前首先判断当前实例状态，如果是pending状态，则可以改变为成功，如果非pending，则终止执行，只可变更一次
    if (this.status !== PENDING) return false;
    this.status = FULFILLED; // 此时将状态变更为成功状态
    this.value = value; // 记录当前成功结果
    // 执行完以上操作后需要执行实例化时传递的成功回调，因是数组因此需要循环执行，从数组开始依次取出执行
    while (this.successCallback.length) this.successCallback.shift()(); // 通过shift()方法从数组中取出第一个并执行，原数组会被改变，长度-1
  }

  // 失败回调，接受一个参
  reject = reason => {
    // 与成功相同，需先判断,以下代码同成功相同逻辑
    if (this.status !== PENDING) return false;
    this.status = REJECTED;
    this.reason = reason;
    while (this.faillCallback.length) this.faillCallback.shift()();
  }

  // 状态确定后回调，最重要的方法
  then = (successCallback, faillCallback) => {
    successCallback = successCallback ? successCallback : value => value; // 判断是否存在，存在则使用，不存在则创建一个默认函数，失败同理
    faillCallback = faillCallback ? faillCallback : reason => { throw reason }; // 不同点在于，失败时的原因通过throw进行抛出错误，会中停止执行

    // 因then方法可以链式调用，因此返回的结果必然是一个promise对象,因此首先要创建一个Promise对象
    const newPromise = new MyPromise((resolve, reject) => {
      /**
       * 辅助函数 - 结果状态回调
       * 需要传递当前结果值，需要执行的函数
       * 需要执行的函数进行之后后，拿着结果去判断状态，并进行后续处理
      */
      function waitingCallback(current, callback) {
        setTimeout(() => {
          // 进行错误兼容
          try {
            const result = callback(current);
            resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(current);
          }
        }, 0);
      }
      
      if (this.status === FULFILLED) {
        // 当状态为成功时处理
        waitingCallback(this.value, successCallback);
      } else if (this.status === REJECTED) {
        // 当状态为失败时处理
        waitingCallback(this.reason, faillCallback);
      } else {
        /**
         * 当状态为pending状态时处理
         * pending状态代表还没有完成当前请求，因此需要将成功与失败进行存储，已遍等结束后调用
        */
        this.successCallback.push(() => {
          waitingCallback(this.value, successCallback);
        });

        this.faillCallback.push(() => {
          waitingCallback(this.reason, faillCallback);
        });
      }
    });

    return newPromise;
  }

  // 静态resolve方法
  static resolve (value) {
    if (value instanceof MyPromise) return value; // 如果是promise实例，则直接返回
    return new MyPromise(resolve => resolve(value)); // 普通值这直接实例化一个新promise，并且使用resolve将其包裹返回
  }

  static all (array) {
    const result = []; // 结果数组
    let index = 0; // 计数器，用于对比结果长度和参数长度是否相等
    // all方法也可then操作。则返回类型也是promise，因此直接实例化并返回
    return new MyPromise((resolve, reject) => {
      // 数组添加数据方法 - 辅助函数，当计数器和参数长度相等时返回结果
      function addData (key, value) {
        result[key] = value;
        index++;
        if (index === array.length) {
          resolve(result);
        }
      }

      // 遍历数组每一个
      for (let i = 0; i< array.length; i++) {
        const current = array[i];
        // 判断当前是否为实例，是则调用then去向结果内增加数据，否则直接返回错误信息
        if (current instanceof MyPromise) {
          current.then(value => addData(i, value), reason => reject(reason));
        } else {
          addData(i, current);
        }
      }
    });
  }

  finally (callback) {
    return this.then(value => {
      // 已回调函数的结果作为resolve的参数
      return MyPromise.resolve(callback()).then(() => value);
    }, reason => {
      return MyPromise.resolve(callback()).then(() => { throw reason });
    });
  }

  catch (faillCallback) {
    return this.then(undefined, faillCallback); // 直接调用then方法，并只传递错误回调
  }
}



// 辅助函数 - 判断resolve 值类型
function resolvePromise(newPromise, result, resolve, reject) {
  if (newPromise === result) {
    return reject(new TypeError('Chaining cycle detcted for promise #<Promise>')); // 如果回调中返回的结果为自身，则抛出类型错误，并终止执行
  }

  // 如果回调返回的结果为Promise的实例
  if (result instanceof MyPromise) {
    result.then(resolve, reject); // 则将结果直接调用then函数进行判断处理,并将成功、失败进行传递
  } else {
    // 如果返回的结果为普通值，则直接将结果以成功状态返回,回参为当前结果
    resolve(result);
  }
}

module.exports = MyPromise; // 遵循node模块规范，通过module.export进行导出